# 期货策略模版 - 布林带窄幅突破策略
# 简单布林策略:当布林线上下轨收口较窄后，价格突破布林上轨做多，反之做空。价格跌破布林下轨多单平仓，反之空单平仓。

# 初始化函数,全局只运行一次
def init(context):
    # 设立商品期货子账户,其中stock为0，future为100000
    set_subportfolios([{"cash": 0, "type": 'stock'}, {"cash": 100000, "type": "future"}])
    log.info('策略开始运行,初始化函数全局只运行一次')
    
    # 设置期货手续费和滑点
    set_commission(PerShare(type='future', cost=0.000045))
    set_slippage(PriceSlippage(0.005), 'future')
    
    # 设置螺纹钢保证金比例
    set_margin_rate('RB', 0.08, 0.09)
    
    # 设置成交量限制
    set_volume_limit(0.25, 0.5)
    
    # 设置交易标的和布林带参数
    context.ins = 'RB9999'
    context.boll_period = 48      # 布林带周期
    context.boll_dev = 1.8        # 标准差倍数
    context.boll_narrow = 1.011   # 收口阈值
    
    # 设置基准和订阅品种
    set_benchmark(context.ins)
    subscribe(context.ins)

# 每日开盘前运行
def before_trading(context):
    date = get_datetime().strftime('%Y-%m-%d %H:%M:%S')
    log.info('{} 盘前运行'.format(date))

# 核心交易逻辑
def handle_bar(context, bar_dict):
    time = get_datetime().strftime('%Y%m%d %H%M')
    log.info('{} 盘中运行'.format(time))
    
    # 获取合约代码和价格数据
    ins = context.ins
    prices = get_price_future(ins, None, time, '1d', ['close', 'open'], bar_count=context.boll_period + 1)
    
    # 检查数据是否足够
    if len(prices) < context.boll_period:
        log.info('历史数据不足，跳过当前bar')
        return
    
    # 获取当前价格和前收价
    current_close = bar_dict[ins].close
    current_open = bar_dict[ins].open
    prev_close = prices['close'][-2]  # 前一根K线收盘价
    
    # 计算布林带指标
    closes = prices['close'][:-1]  # 排除当前K线
    ma = closes.mean()
    std = closes.std()
    upper = ma + context.boll_dev * std
    lower = ma - context.boll_dev * std
    
    # 计算布林带收口条件
    boll_width_ratio = upper / lower
    is_narrow = boll_width_ratio < context.boll_narrow
    
    # 计算开盘价波动条件
    is_small_gap = False
    if current_open > prev_close:
        is_small_gap = (current_open / prev_close < 1.001)
    elif current_open < prev_close:
        is_small_gap = (prev_close / current_open < 1.001)
    else:
        is_small_gap = True
    
    # 获取当前持仓
    future_account = context.portfolio.future_account
    long_amount = future_account.positions[ins].long_amount
    short_amount = future_account.positions[ins].short_amount
    
    # 交易信号判断
    # 1. 多单平仓条件：价格跌破下轨
    if long_amount > 0 and current_close < lower:
        order_future(ins, long_amount, 'close', 'long', None)
        log.info('价格跌破布林下轨，平多单')
    
    # 2. 空单平仓条件：价格突破上轨
    elif short_amount > 0 and current_close > upper:
        order_future(ins, short_amount, 'close', 'short', None)
        log.info('价格突破布林上轨，平空单')
    
    # 3. 开多条件：布林收口+价格突破上轨+开盘小缺口
    elif (current_close > upper and 
          is_narrow and 
          is_small_gap and 
          long_amount == 0):
        order_future(ins, 20, 'open', 'long', None)
        log.info('布林收口窄幅+突破上轨，开多仓')
    
    # 4. 开空条件：布林收口+价格跌破下轨+开盘小缺口
    elif (current_close < lower and 
          is_narrow and 
          is_small_gap and 
          short_amount == 0):
        order_future(ins, 20, 'open', 'short', None)
        log.info('布林收口窄幅+跌破下轨，开空仓')

# 收盘后运行
def after_trading(context):
    time = get_datetime().strftime('%Y-%m-%d %H:%M:%S')
    log.info('{} 盘后运行'.format(time))
    log.info('一天结束')